using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using System.Text;

using Open.Data.Persistence.Hashes;

namespace Open.Data.Persistence
{
    public static class Common
    {
        private static Dictionary<Type, bool> _allowedTypes;

        public static bool IsAllowedType(Type type)
        {
            if (type.IsEnum) return true;
            if (typeof(IPersistable).IsAssignableFrom(type)) return true;

            //Create list of allowed types
            if (_allowedTypes == null) CreateAllowedTypes();

            return _allowedTypes.ContainsKey(type);
        }

        public static List<Type> GetAllowedTypes()
        {
            //Create list of allowed types
            if (_allowedTypes == null) CreateAllowedTypes();

            List<Type> results = new List<Type>();

            foreach (Type type in _allowedTypes.Keys)
            {
                results.Add(type);
            }

            return results;
        }

        //Get the storage name for the type formatted without the prefix
        public static string GetTypeName(Type type, string prefix)
        {
            string fullname = type.FullName;

            if (prefix == null || prefix == string.Empty || !fullname.StartsWith(prefix)) return fullname;
            fullname = fullname.Replace(prefix, "");
            if (fullname.StartsWith(".")) fullname = fullname.Substring(1);

            return fullname;
        }

        //Get the name of a query
        public static string GetQueryName(IQuery query)
        {
            return query.GetHashCode().ToString().Replace("-", "0");
        }

        public static Type GetPersistType(IPersistable graph)
        {
            if (graph is IView)
            {
                IView view = (IView) graph;
                return view.UnderlyingType();
            }

            return graph.GetType();
        }

        public static Type GetPersistType(Type type)
        {
            if (!typeof(IView).IsAssignableFrom(type)) return type;

            //Get the view type
            while (!type.IsGenericType)
            {
                type = type.BaseType;
            }

            //Get the type used in the generic
            return type.GetGenericArguments()[0];
        }

        public static PropertyInfo[] GetCommonProperties(PropertyInfo[] a, PropertyInfo[] b)
        {
            List<PropertyInfo> list = new List<PropertyInfo>();

            foreach (PropertyInfo prop in a)
            {
                foreach (PropertyInfo prop2 in b)
                {
                    if (prop.Name == prop2.Name)
                    {
                        list.Add(prop);
                        break;
                    }
                }
            }

            return list.ToArray();
        }

        //Determines if the parent type is the first concrete type of an abstract type
        public static bool IsConcreteTypeOf(Type parentType, Type abstractType)
        {
            if (parentType == abstractType) return true;
            if (parentType.BaseType == null) return false;
            if (!parentType.BaseType.IsAbstract) return false;
            return (IsConcreteTypeOf(parentType.BaseType, abstractType));
        }

        //Returns the query at the top of the query tree
        public static IQuery GetRootQuery(IQuery query)
        {
            if (query.Parent == null) return query;

            return GetRootQuery(query.Parent);
        }

        //Returns a relevant label for the query provided matching it's place in the query tree
        public static LabelHash GetQueryLabel(IQuery query)
        {
            IQuery root = GetRootQuery(query);

            return FindQueryLabel(query, root, new QueryLabelHash(root));
        }

        //Check if the query provided is the target query and calculate it's hash from the parent query hashes
        private static LabelHash FindQueryLabel(IQuery target, IQuery current, LabelHash parent)
        {
            if (current == target) return new TypeLabelHash(target.Type, parent);
            if (current.SubQueries == null || current.SubQueries.Count == 0) return null;

            LabelHash result = null;

            foreach (IQuery subQuery in current.SubQueries)
            {
                result = FindQueryLabel(target, subQuery, new QueryLabelHash(subQuery, parent));
                if (result != null) return result;
            }

            return result;
        }

        private static void CreateAllowedTypes()
        {
            _allowedTypes = new Dictionary<Type, bool>();

            _allowedTypes.Add(typeof(string), true);
            _allowedTypes.Add(typeof(long), true);
            _allowedTypes.Add(typeof(int), true);
            _allowedTypes.Add(typeof(DateTime), true);
            _allowedTypes.Add(typeof(bool), true);
            _allowedTypes.Add(typeof(byte), true);
            _allowedTypes.Add(typeof(short), true);
            _allowedTypes.Add(typeof(double), true);
            _allowedTypes.Add(typeof(float), true);
            _allowedTypes.Add(typeof(decimal), true);
            _allowedTypes.Add(typeof(char), true);
            _allowedTypes.Add(typeof(System.Guid), true);

            _allowedTypes.Add(typeof(Nullable<long>), true);
            _allowedTypes.Add(typeof(Nullable<int>), true);
            _allowedTypes.Add(typeof(Nullable<DateTime>), true);
            _allowedTypes.Add(typeof(Nullable<bool>), true);
            _allowedTypes.Add(typeof(Nullable<byte>), true);
            _allowedTypes.Add(typeof(Nullable<short>), true);
            _allowedTypes.Add(typeof(Nullable<double>), true);
            _allowedTypes.Add(typeof(Nullable<float>), true);
            _allowedTypes.Add(typeof(Nullable<decimal>), true);
            _allowedTypes.Add(typeof(Nullable<char>), true);
            _allowedTypes.Add(typeof(Nullable<System.Guid>), true);

            _allowedTypes.Add(typeof(Stream), true);
        }

        public static void WriteComparisonOperator(StringBuilder builder, ComparisonOperator op)
        {
            if (op == ComparisonOperator.Equal)
            {
                builder.Append("= ");
            }
            else if (op == ComparisonOperator.GreaterThan)
            {
                builder.Append("> ");
            }
            else if (op == ComparisonOperator.GreaterThanEqual)
            {
                builder.Append(">= ");
            }
            else if (op == ComparisonOperator.LessThan)
            {
                builder.Append("< ");
            }
            else if (op == ComparisonOperator.LessThanEqual)
            {
                builder.Append("<= ");
            }
            else if (op == ComparisonOperator.NotEqual)
            {
                builder.Append("<> ");
            }
            else if (op == ComparisonOperator.Like)
            {
                builder.Append("LIKE ");
            }
        }

        public static void WriteConditionsOperator(StringBuilder builder, ConditionsOperator op)
        {
            if (op == ConditionsOperator.And)
            {
                builder.Append("AND ");
            }
            else
            {
                builder.Append("OR ");
            }
        }

        public static void WriteExpressionOperator(StringBuilder builder, ExpressionOperator op)
        {
            if (op == ExpressionOperator.Add)
            {
                builder.Append("+ ");
            }
            else if (op == ExpressionOperator.Divide)
            {
                builder.Append("/ ");
            }
            else if (op == ExpressionOperator.Modulo)
            {
                builder.Append("% ");
            }
            else if (op == ExpressionOperator.Multiply)
            {
                builder.Append("* ");
            }
            else if (op == ExpressionOperator.Subtract)
            {
                builder.Append("- ");
            }
        }
    }
}
